/*
 * Copyright (c) 2022 Shenzhen Kaihong Digital Industry Development Co., Ltd.
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 * http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

#include "se_napi_oma_common.h"

#include <string>

namespace OHOS {
namespace SE {
namespace KITS {
std::string NapiSEOmaCommon::keyCode = "code";
std::string NapiSEOmaCommon::sePermDesc = "ohos.permission.MANAGE_SECURE_SETTINGS";
const std::map<SEErrorCode, std::string> seErrorInfoMap {
    {BUSI_ERR_PARAM_EXCEPTION, "The parameter check failed."},
    {BUSI_ERR_CAPABILITY_EXCEPTION, "Capability not supported."},
    {BUSI_ERR_SE_STATE_EXCEPTION, "Illegal service state exception."},
    {BUSI_ERR_SE_ELEMENT_EXCEPTION, "No such element exception."},
    {BUSI_ERR_SE_ACCESS_EXCEPTION, "Illegal access rule exception."},
    {BUSI_ERR_SE_IO_EXCEPTION, "Secure element IO exception."}
};

template<typename T>
napi_value NapiSEOmaCommon::JSOmaClassConstructor(napi_env env, napi_callback_info info)
{
    napi_value thisVar = nullptr;
    napi_get_cb_info(env, info, nullptr, nullptr, &thisVar, nullptr);
    T* omaJSClass = new T();

    napi_wrap(
        env, thisVar, omaJSClass,
        [](napi_env env, void* data, void* hint) {
            T* service = (T*)data;
            if (service) {
                delete service;
                service = nullptr;
            }
        },
        nullptr,
        nullptr);
    return thisVar;
}

napi_value NapiSEOmaCommon::CreateUndefined(napi_env env)
{
    napi_value result = nullptr;
    NAPI_CALL(env, napi_get_undefined(env, &result));
    return result;
}

std::string NapiSEOmaCommon::BuildErrorMessage(int errCode, std::string funcName, std::string forbiddenPerm,
    std::string paramName, std::string expertedType)
{
    DebugLog("enter function name NapiSEOmaCommon::BuildErrorMessage %{public}d", errCode);
    std::string errMsg;
    if (errCode == BUSI_ERR_PERM_EXCEPTION) {
        return errMsg.append("Permission denied. An attempt was made to ${")
            .append(funcName)
            .append("} forbidden by permission: ${")
            .append(forbiddenPerm)
            .append("}.");
    }
    SEErrorCode code = static_cast<SEErrorCode>(errCode);
    auto it = seErrorInfoMap.find(code);
    if (it != seErrorInfoMap.end()) {
        return it->second;
    } else {
        return "Unknown error message";
    }
}

napi_value NapiSEOmaCommon::GenerateBusinessError(const napi_env &env, int errCode, const std::string &errMessage)
{
    napi_value code = nullptr;
    napi_create_uint32(env, errCode, &code);
    napi_value message = nullptr;
    napi_create_string_utf8(env, errMessage.c_str(), NAPI_AUTO_LENGTH, &message);
    napi_value businessError = nullptr;
    napi_create_error(env, nullptr, message, &businessError);
    napi_set_named_property(env, businessError, keyCode.c_str(), code);
    return businessError;
}

bool NapiSEOmaCommon::CheckSEStatusCodeAndThrow(const napi_env &env, int statusCode, const std::string funcName)
{
    DebugLog("enter function name NapiSEOmaCommon::CheckSEtatusCodeAndThrow %{public}d", statusCode);
    if (statusCode == SEErrorCode::ERR_NONE) {
        return true;
    } else if (statusCode == BUSI_ERR_PERM_EXCEPTION) {
        napi_throw(env, GenerateBusinessError(env, statusCode,
            BuildErrorMessage(statusCode, funcName, sePermDesc, EMPTY_STR, EMPTY_STR)));
    } else if (statusCode == BUSI_ERR_CAPABILITY_EXCEPTION || statusCode == BUSI_ERR_PARAM_EXCEPTION) {
        napi_throw(env, GenerateBusinessError(env, statusCode,
            BuildErrorMessage(statusCode, funcName, EMPTY_STR, EMPTY_STR, EMPTY_STR)));
    } else if (statusCode >= SEErrorCode::BUSI_ERR_SE_STATE_EXCEPTION) {
        napi_throw(env, GenerateBusinessError(env, statusCode,
            BuildErrorMessage(statusCode, EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR)));
    }
    return false;
}

void NapiSEOmaCommon::CheckArgCountAndThrow(const napi_env &env, int argCount, int expCount)
{
    if (argCount != expCount) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
            EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR)));
    }
}

bool NapiSEOmaCommon::CheckArrayNumberAndThrow(const napi_env &env,
                                               const napi_value &param,
                                               const std::string &argName,
                                               const std::string &argType)
{
    if (!IsNumberArray(env, param)) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
            EMPTY_STR, EMPTY_STR, argName, argType)));
        return false;
    }
    return true;
}

bool NapiSEOmaCommon::CheckStringAndThrow(const napi_env &env,
                                          const napi_value &param,
                                          const std::string &argName,
                                          const std::string &argType)
{
    if (!IsTargetValueType(env, param, napi_valuetype::napi_string)) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
            EMPTY_STR, EMPTY_STR, argName, argType)));
        return false;
    }
    return true;
}

void NapiSEOmaCommon::ThrowParameterError(const napi_env &env)
{
    napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
        EMPTY_STR, EMPTY_STR, EMPTY_STR, EMPTY_STR)));
}

bool NapiSEOmaCommon::CheckNumberAndThrow(const napi_env &env,
                                          const napi_value &param,
                                          const std::string &argName,
                                          const std::string &argType)
{
    if (!IsTargetValueType(env, param, napi_valuetype::napi_number)) {
        napi_throw(env, GenerateBusinessError(env, BUSI_ERR_PARAM_EXCEPTION, BuildErrorMessage(BUSI_ERR_PARAM_EXCEPTION,
            EMPTY_STR, EMPTY_STR, argName, argType)));
        return false;
    }
    return true;
}

bool NapiSEOmaCommon::IsNumberArray(const napi_env &env, const napi_value &param)
{
    if (!IsArray(env, param)) {
        return false;
    }

    uint32_t arrayLength = 0;
    napi_get_array_length(env, param, &arrayLength);
    napi_value elementValue = nullptr;
    for (uint32_t i = 0; i < arrayLength; ++i) {
        napi_get_element(env, param, i, &elementValue);
        napi_valuetype elementType = napi_undefined;
        napi_typeof(env, elementValue, &elementType);
        if (elementType != napi_number) {
            return false;
        }
    }
    return true;
}

bool NapiSEOmaCommon::IsObjectArray(const napi_env &env, const napi_value &param)
{
    if (!IsArray(env, param)) {
        return false;
    }

    uint32_t arrayLength = 0;
    napi_get_array_length(env, param, &arrayLength);
    napi_value elementValue = nullptr;
    for (uint32_t i = 0; i < arrayLength; ++i) {
        napi_get_element(env, param, i, &elementValue);
        napi_valuetype elementType = napi_undefined;
        napi_typeof(env, elementValue, &elementType);
        if (elementType != napi_object) {
            return false;
        }
    }
    return true;
}

bool NapiSEOmaCommon::IsArray(const napi_env &env, const napi_value &param)
{
    bool arrayType = false;
    napi_status status = napi_is_array(env, param, &arrayType);
    if (status != napi_ok || !arrayType) {
        ErrorLog("para type is not array");
        return false;
    }

    uint32_t arrayLength = 0;
    napi_get_array_length(env, param, &arrayLength);
    if (arrayLength == 0) {
        ErrorLog("array length can't be 0");
        return false;
    }
    return true;
}

bool NapiSEOmaCommon::IsTargetValueType(const napi_env &env, const napi_value &param, napi_valuetype targetType)
{
    napi_valuetype valueType = napi_undefined;
    napi_typeof(env, param, &valueType);
    return valueType == targetType;
}

bool NapiSEOmaCommon::ParseBytesVector(napi_env env, std::vector<unsigned char> &vec, napi_value args)
{
    bool isArray = false;
    napi_status status = napi_is_array(env, args, &isArray);
    if (status != napi_ok || !isArray) {
        ErrorLog("ParseBytesVector, not array");
        return false;
    }
    uint32_t arrayLength = 0;
    napi_get_array_length(env, args, &arrayLength);
    for (uint32_t i = 0; i < arrayLength; i++) {
        napi_value element = nullptr;
        napi_get_element(env, args, i, &element);

        napi_valuetype valueType = napi_undefined;
        napi_typeof(env, element, &valueType);
        if (valueType != napi_number) {
            ErrorLog("ParseBytesVector, not number!");
            return false;
        }

        uint32_t byteValue = 0x0;
        napi_get_value_uint32(env, element, &byteValue);
        vec.push_back(static_cast<unsigned char>(byteValue));
    }
    return true;
}

void NapiSEOmaCommon::Int32VectorToJS(napi_env env, std::vector<int32_t>& src, napi_value &result)
{
    if (src.empty()) {
        ErrorLog("BytesVectorToJS src empty");
        napi_create_array_with_length(env, 0, &result);
        return;
    }
    size_t idx = 0;
    napi_create_array_with_length(env, src.size(), &result);
    for (auto& num : src) {
        napi_value obj = nullptr;
        napi_create_int32(env, num, &obj);
        napi_set_element(env, result, idx, obj);
        idx++;
    }
}

napi_value NapiSEOmaCommon::GetNamedProperty(napi_env env, napi_value object, const std::string &propertyName)
{
    napi_value value = nullptr;
    NAPI_CALL(env, napi_get_named_property(env, object, propertyName.c_str(), &value));
    return value;
}

napi_valuetype NapiSEOmaCommon::GetValueType(napi_env env, napi_value value)
{
    if (value == nullptr) {
        return napi_undefined;
    }

    napi_valuetype valueType = napi_undefined;
    NAPI_CALL_BASE(env, napi_typeof(env, value, &valueType), napi_undefined);
    return valueType;
}

std::string NapiSEOmaCommon::GetStringFromValue(napi_env env, napi_value value)
{
    constexpr int32_t maxTextLength = 4096;
    char msgChars[maxTextLength] = {0};
    size_t msgLength = 0;
    NAPI_CALL_BASE(env, napi_get_value_string_utf8(env, value, msgChars, maxTextLength, &msgLength), EMPTY_STR);
    if (msgLength > 0) {
        return std::string(msgChars, 0, msgLength);
    } else {
        return EMPTY_STR;
    }
}
} // namespace KITS
} // namespace SE
} // namespace OHOS
